﻿using MongoDB.Driver;
using Sustainalytics.ClientDW.DataAccessLayer;
using Sustainalytics.ClientDW.Entities.Universes;
using Sustainalytics.DataLayer;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESGRatings.BusinessLogic;
using Sustainalytics.Synchronization.Interface;
using System;
using System.Configuration;
using System.Linq;

namespace Sustainalytics.ClientDW.Synchronization
{
    using ESGRatings.DataLayer;
    using MongoDB.Extensions;
    using Sustainalytics.ClientDW.Entities;
    using Sustainalytics.DataLayer.EsgRatings;
    using System.Threading.Tasks;

    public class ProcessedFilesSyncStep : ISyncStep
    {
        private IEsgInputFileRepository _esgInputFileRepository;
        private IUniversesRepository _universesRepository;
        private TemporaryDataSetBusinessLogic _dataSetBusinessLogic;
        private CorporateDataCleaner _corporateDataCleaner;

        public ProcessedFilesSyncStep(
            IEsgInputFileRepository repository, 
            IUniversesRepository universesRepository, 
            TemporaryDataSetBusinessLogic dataSet)
        {
            _esgInputFileRepository = repository;
            _universesRepository = universesRepository;
            _dataSetBusinessLogic = dataSet;
        }

        public ProcessedFilesSyncStep()
        {
        }

        public SyncStepOrder ExecutionOrder => SyncStepOrder.ProcessedFiles;

        public SyncContext Start(SyncContext syncContext)
        {
            CreateRepositoryFromConnString(syncContext);
            syncContext.Token.ThrowIfCancellationRequested();
            var writeInCollection = true;

            if (syncContext.ErrorPolicy == ErrorPolicy.CarryOn &&
                syncContext.PreviousChanges.Any(
                    a => a.AffectedObjectName == ChangedElement.AFFECTED_SYNC_RESULT_NAME_OBJECT))
            {
                syncContext.Log("An error ocurred during previous sync steps,the input files will not be marked as processed in their collections");
                writeInCollection = false;
            }
            var elementsInput =
                syncContext.PreviousChanges.Where(
                    a => a.AffectedObjectName == ChangedElement.AFFECTED_INPUT_FILE_NAME_OBJECT).ToList();
            foreach (var changedElement in elementsInput)
            {
                syncContext.Token.ThrowIfCancellationRequested();
                if (changedElement.AffectedDocument == null) continue;
                ProcesChangedElement(changedElement, writeInCollection, syncContext);
            }
            if (syncContext.PreviousChanges.Any(a => a.AffectedObjectName == ChangedElement.ARCHIVE_DATA_SET_ID)) //than we need to mark a dataset to be preserved
            {
                var firstOrDefault = syncContext.PreviousChanges.FirstOrDefault(
                    a => a.AffectedObjectName == ChangedElement.ARCHIVE_DATA_SET_ID);
                if (firstOrDefault != null)
                {
                    var guidPreserve =
                        firstOrDefault.AffectedDocumentId;
                    syncContext.Log(string.Format("Marking the set id {0} to be preserved", guidPreserve));
                    _dataSetBusinessLogic.MarkSetAsPreserved(guidPreserve).Wait();
                }
            }
            var usedDays = 40;
            var days = ConfigurationManager.AppSettings["dsDaysOfPreservation"] ?? string.Empty;
            if (!string.IsNullOrEmpty(days)) usedDays = int.Parse(days);
            syncContext.Log(string.Format("Starting the deletion of data set not preserved and older than {0}", usedDays));
            var output = _dataSetBusinessLogic.CleanupDataSet(usedDays).Result;
            foreach (var item in output.Item2)
            {
                syncContext.Log(item);
            }

            _corporateDataCleaner.SetResearchNotAvailable().Wait();
            _corporateDataCleaner.DeleteCompaniesWithNoResearchData();

            UpdateCompanyNamesAsync().GetAwaiter();

            syncContext.Log("Updated company names according to CorporateData");

            return syncContext;
        }

        public async Task UpdateCompanyNamesAsync()
        {
            var corporateData = MongoFactory.GetCollectionFromDatabase<CorporateData>(MongoFactory.GetConnectionStrings().ClientDW);
            var gcCompanySummary = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummary>(MongoFactory.GetConnectionStrings().ClientDW);
            var gcCompanySummaryextended = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummaryExtended>(MongoFactory.GetConnectionStrings().ClientDW);

            var corpdatalist = corporateData.Find(_ => true)
                               .Project(x => new
                               {
                                   x.Id,
                                   x.CompanyName,
                                   x.CompanyNameUrlFriendly,
                                   x.CompanyNameLowercase
                               })
                               .ToList()
                               .ToDictionary(y => y.Id, y => new { y.CompanyName, y.CompanyNameUrlFriendly, y.CompanyNameLowercase });

            for (int i = 0; i < corpdatalist.Count - 1; i++)
            {
                var filter3 = Builders<GCOverallCompanySummary>.Filter.Eq(x => x.CompanyProfile.Id, corpdatalist.ElementAt(i).Key);
                var update3 = Builders<GCOverallCompanySummary>.Update.Set(x => x.CompanyProfile.Name, corpdatalist.ElementAt(i).Value.CompanyName)
                                                                         .Set(x => x.CompanyProfile.NameLowercase, corpdatalist.ElementAt(i).Value.CompanyNameLowercase)
                                                                         .Set(x => x.CompanyProfile.NameUrlFriendly, corpdatalist.ElementAt(i).Value.CompanyNameUrlFriendly);
                await gcCompanySummary.UpdateManyAsync(filter3, update3);

                var filter4 = Builders<GCOverallCompanySummaryExtended>.Filter.Eq(x => x.CompanyProfile.Id, corpdatalist.ElementAt(i).Key);
                var update4 = Builders<GCOverallCompanySummaryExtended>.Update.Set(x => x.CompanyProfile.Name, corpdatalist.ElementAt(i).Value.CompanyName)
                                                                         .Set(x => x.CompanyProfile.NameLowercase, corpdatalist.ElementAt(i).Value.CompanyNameLowercase)
                                                                         .Set(x => x.CompanyProfile.NameUrlFriendly, corpdatalist.ElementAt(i).Value.CompanyNameUrlFriendly);
                await gcCompanySummaryextended.UpdateManyAsync(filter4, update4);
            }
        }

        private void CreateRepositoryFromConnString(SyncContext syncContext)
        {
            if (_esgInputFileRepository != null && _universesRepository != null)
                return;
            var clientPlatformConnectionString = syncContext.GAConnectionStrings["ClientPlatform"];
            var clientUniversesConnString = syncContext.GAConnectionStrings["ClientUniverses"];

            _esgInputFileRepository = EsgInputFileRepository.GetInstance(new FileRepository(clientPlatformConnectionString),
                                                         new RepositoryCollection<EsgInputFile>(clientPlatformConnectionString));
            _universesRepository = UniversesRepository.GetUniversesRepository(clientUniversesConnString);
            var rpHistoryConnectionString = syncContext.GAConnectionStrings["RPHistoricalDataSet"];
            _dataSetBusinessLogic = new TemporaryDataSetBusinessLogic(
                        MongoFactory.GetCollectionFromDatabase<DSCorporateData>(rpHistoryConnectionString),
                        MongoFactory.GetCollectionFromDatabase<DSIndicatorTemplate>(rpHistoryConnectionString),
                        MongoFactory.GetCollectionFromDatabase<DSCompanyRawScores>(rpHistoryConnectionString),
                        MongoFactory.GetCollectionFromDatabase<DSWeightMatrix>(rpHistoryConnectionString),
                        MongoFactory.GetCollectionFromDatabase<DSReferenceUniverse>(rpHistoryConnectionString),
                        MongoFactory.GetCollectionFromDatabase<HistoricalDataSet>(rpHistoryConnectionString),
                        new TemporaryDataSetBuilder());
            _corporateDataCleaner = new CorporateDataCleaner(syncContext);
        }

        private void ProcesChangedElement(ChangedElement element, bool writeInCollection, SyncContext syncContext)
        {
            if (element.AffectedDocument == null) return;
            if (element.AffectedDocument is EsgInputFileComplete)
            {
                var cast = element.AffectedDocument as EsgInputFileComplete;
                DoActionProcessingFile(() => _esgInputFileRepository.MarkFileAsProcessed(cast.Id), writeInCollection, string.Format("The file {0} was marked as processed",
                            cast.FileName), string.Format("The file {0} was marked as processed but was not updated in the collection {1} that is in the database {2}",
                            cast.FileName, element.AffectedCollection, element.AffectedDB), syncContext);
            }
            if (element.AffectedDocument is UploadLog)
            {
                var cast = element.AffectedDocument as UploadLog;
                DoActionProcessingFile(() => _universesRepository.UpdateUploadLog(cast), writeInCollection,
                    string.Format("The file uploaded on: {0} has been successfully processed.",
                        cast.Created.ToString("dd/MM/yyyy HH:mm")),
                    string.Format(
                        "The file uploaded on: {0} was marked as processed but was not updated in the collection {1} that is in the database {2}.",
                        cast.Created.ToString("dd/MM/yyyy HH:mm"), element.AffectedCollection, element.AffectedDB),
                    syncContext
                    );
            }
        }

        private void DoActionProcessingFile(Action actionExecute, bool writeInCollection, string msgSucces, string msgFail, SyncContext syncContext)
        {
            if (writeInCollection)
            {
                actionExecute();
                syncContext.Log(msgSucces);
            }
            else
            {
                syncContext.Log(msgFail);
            }
        }
    }
}
